<- 1 a
2-1. 변수 (Variable)
프로그래밍에서 말하는 변수는 특정 값 또는 데이터를 저장하는 메모리 공간을 말합니다. R에서는 문자형태를 변수로 지정하여 값을 저장할 수 있습니다. 저장하겠다는 명령어는 “<-”나 “=”를 사용합니다. a라는 변수에 숫자 1을 넣어 봅시다.
오른쪽 Environment에 a에 1이 담겨있는 것을 볼 수 있습니다.
console창에 출력하고 싶다면 print를 해봅니다
print(a)
[1] 1
a에 담겨 있던 값 1이 출력되는 것을 볼 수 있습니다. (print(A)도 될까요?)
지금은 변수명을 단순하게 a라고 했습니다. 그런데 코딩이 복잡해지면 변수명을 아무렇게나 했다간 내 코드를 내가 못 알아보는 사태가 발생하고 남이 내 코드를 해석하는데 매우 어려워질 수 있습니다. 그래서 변수명을 적절하게 설정하는 것이 중요합니다. 이를 변수 명명법이라고 합니다. 꽤나 규칙이 필요한데 여기서는 가볍게 무시하고요. 변수는 소문자로 남이 적당히 알아 볼 수 있게만 정해봅시다.
변수명은 영어 소문자로 만듭니다. 숫자가 맨 앞으로 나올 순 없습니다.
파이썬 방식: 긴 단어는 underscore( _ )로 구분합니다. ex) snake_case <- 1
한글도 변수로 쓸 수 있더라고요 그런데 하지 맙시다. 프로그래밍에서 한글은 지양합니다.
상수명은 대문자로 씁니다. ex) MAX_ITER_NUM <- 1000
R에서는 특이하게 변수에 값을 담을 때 <- 를 사용합니다. 일반적으로 쓰는 ” = ” 함수내 옵션을 지정할 때 씁니다.
2-2. Primitive data types
변수의 기본 형태인 logical, numeric, character에 대해 익혀본다.
Logical
<- TRUE
true <- FALSE false
R에서 true/false를 TRUE 또는 T, FALSE 또는 F로 나타냅니다. 저는 보통 TRUE, FALSE로 쓰긴 합니다. 좌항과 우항이 동일한지 확인하는 비교연산자 “==”로 true 변수가 TRUE랑 같은지 확인해봅시다.
== TRUE true
[1] TRUE
== T true
[1] TRUE
true는 FALSE 입니까?
== FALSE true
[1] FALSE
true는 1입니까?
== 1 true
[1] TRUE
true는 TRUE 값인데 1이랑 같다고 하네요? 컴퓨터 공학에서 논리를 표현할 때 0은 false, 1은 true와 동치입니다.
== 0 false
[1] TRUE
Numeric
수를 나타내는 numeric 타입에 대해 알아봅시다. 숫자 관련 타입을 파이썬과 비교하자면 파이썬에는 int와 float으로 나눌 수 있듯 R에서는 integer, double로 나눌 수 있습니다. 그런데 나눠가면서까지 쓸 일은 제 경험상 없습니다. 정수, 실수 등 딱히 나누지 않는다고 생각하시면 됩니다.
<- 1+3+5+7+9 sum_odd
R은 복소수 형태(complex)도 있습니다. 물론 우리는 아마도 안 쓸 것입니다만 확인만 해보죠. 복소수 i 단독으로는 쓸 수 없습니다. 적어도 1i라고 해줘야 합니다.
<- 1i c_num
변수의 타입이 궁금하면 typeof() 함수를 쓰거나, 복소수인지를 물어보고 싶을땐 is.complex() 함수를 씁니다.
typeof(c_num)
[1] "complex"
is.complex(c_num)
[1] TRUE
아래는 오일러 공식입니다. \(e^{i\pi}\)가 -1인지 확인해봅시다.
\[ e^{i\pi}=-1 \]
<- as.numeric(exp(1i*pi)) == -1 euler_identity
Warning: imaginary parts discarded in coercion
print(euler_identity)
[1] TRUE
Character (String)
문자(문자열)를 의미합니다. 따옴표 ’ ’, 쌍따옴표 ” “로 나타냅니다.
<- "a"
a <- "b"
b <- "c"
c1 <- "d" d
변수로 c 를 쓰진 마세요! c는 R에서 벡터를 만들기 위한 아주 중요한 함수입니다. 우선 문자열에 대해 배워봅시다. 문자가 숫자로만 되어 있다면 numeric(수)로 바꾸는 것도 가능합니다.
<- "123"
num as.numeric(num) + 1
[1] 124
그냥 더하는 것은 안됩니다.
#num + 1
R은 자연어처리(NLP)도 꽤나 잘합니다. 두 문자열을 합치는 paste함수가 있습니다. sep 옵션으로 문장을 합칠 때 사이에 무엇을 넣을지를 정할 수 있습니다
<- "abcd"
str1 <- "efgh"
str2
paste(str1, str2, sep = "!")
[1] "abcd!efgh"
R에서 기본적으로 제공하는 NLP 함수들이 있지만 stringr 패키지가 유용합니다. 이를 불러와 봅니다.
library(stringr)
NLP는 저희의 scope에서 벗어나 소개만 드립니다. 자세한 설명은 아래를 참고하세요.
https://stringr.tidyverse.org/
<- "Heart disease describes a range of conditions that affect the heart. Heart diseases include: Blood vessel disease, such as coronary artery disease; Irregular heartbeats (arrhythmias); Heart problems you're born with (congenital heart defects); Disease of the heart muscle; Heart valve disease."
str3 <- str_split(str3, ":")
str4 str_split(str4[[1]][2], ";")[[1]]
[1] " Blood vessel disease, such as coronary artery disease"
[2] " Irregular heartbeats (arrhythmias)"
[3] " Heart problems you're born with (congenital heart defects)"
[4] " Disease of the heart muscle"
[5] " Heart valve disease."
2-3. Basic data structures
데이터들을 더 다채롭게 담기 위해 R에서는 기본적으로 다음과 같은 자료구조가 있습니다. vector, list, dataframe, matrix, array, factor 입니다.
Vector
R에서 벡터는 여러 개의 데이터를 담는 가장 기본적인 구조입니다. c() 함수로 하는 것이 간편하게 담을 수 있습니다.
<- c(1, 2, 3, 4, 5)
my_data my_data
[1] 1 2 3 4 5
length(my_data) # 벡터의 원소(element)의 수
[1] 5
서로 다른 데이터 타입이 들어갈 경우 에러는 나지 않고 하나의 타입으로 통일됩니다.
<- c(1, 2, "a")
my_data my_data
[1] "1" "2" "a"
class(my_data)
[1] "character"
여러 벡터를 합칠 수도 있습니다.
<- c(1, 2, 3)
my_data1 <- c(4, 5, 6)
my_data2 <- c(my_data1, my_data2)
my_data3 my_data3
[1] 1 2 3 4 5 6
데이터 탐색 (Indexing)
데이터가 많은 경우 모든 데이터를 살펴보기란 힘듭니다. 이 때, 자신이 보고 싶은 조건으로 해당 영역의 데이터만을 확인할 수 있습니다.
<- 1:500 * 2 # 2 ~ 1000 까지의 짝수 생성
my_data length(my_data) # my_data 데이터의 개수
[1] 500
head(my_data) # 데이터의 가장 앞에 있는 n개의 데이터만 확인할 수 있습니다.
[1] 2 4 6 8 10 12
head(my_data, 10) # 앞의 10개를 보고 싶다면
[1] 2 4 6 8 10 12 14 16 18 20
tail(my_data) # 가장 뒤에 있는 데이터를 보고 싶다면
[1] 990 992 994 996 998 1000
인덱싱은 대괄호를 사용합니다. n번째 데이터를 보고 싶다면
1] my_data[
[1] 2
100] my_data[
[1] 200
<- 3
num my_data[num]
[1] 6
length(my_data)] my_data[
[1] 1000
** 여러 프로그래밍 언어와 다르게 R의 인덱스는 0이 아닌 1부터 시작합니다!!! 다른 언어를 하신 분들은 헷갈릴 수 있습니다.
논리 연산자를 사용해 특정 조건에 해당하는 데이터를 뽑을 수 있습니다.
> 960] my_data[my_data
[1] 962 964 966 968 970 972 974 976 978 980 982 984 986 988 990
[16] 992 994 996 998 1000
대괄호는 계속 이어서 쓸 수 있습니다.
> 960][1:3] my_data[my_data
[1] 962 964 966
which함수는 조건에 참에 해당하는 데이터의 인덱스를 알려줍니다. 즉, 데이터 순서로 몇 번째에 있는지 알려줍니다.
which(my_data == 2)
[1] 1
which(my_data == 1) # my_data에 1이 없으니까 아무것도 없다는 의미의 integer(0)을 반환합니다.
integer(0)
결측값 (missing value)
실제로 데이터를 다루다보면 내가 목표하는 자료들을 완벽하게 수집하는 것은 쉽지 않습니다. 결측값은 늘 생기기 마련입니다. 일반적으로 분석 방법은 결측값이 없는 완전한 자료를 기준으로 행해집니다. 그러므로 결측값을 적절하게 처리하는 것이 매우 중요합니다. 그러기 위해 여러 개념과 방법론(missing imputation)이 있지만 우리 강의의 scope을 벗어나기 때문에 여기서는 대체로 결측값이 있는 sample(환자, 세포 등)은 단순 제외할 것입니다.
R 에서 결측값을 NA(not available)로 표현합니다. NA는 꼭 블랙홀 같습니다.
NA + 1
[1] NA
NA * 5
[1] NA
NA == NA
[1] NA
NA == TRUE
[1] NA
그래서 NA는 주의해서 다뤄야 합니다. 데이터를 가공하다보면 우리가 생각하지 못한 결측이 있을 수도 있고 의도하지 않게 결측이 생길 수 있습니다. 그러므로 결측이 절대 있을 수 없는 변수(열)은 꼭 체크하는 습관이 필요합니다.
<- c(52, 63, 34, 87, NA)
age age
[1] 52 63 34 87 NA
is.na(age)
[1] FALSE FALSE FALSE FALSE TRUE
which(is.na(age))
[1] 5
!is.na(age)] age[
[1] 52 63 34 87
-which(is.na(age))] age[
[1] 52 63 34 87
na.omit(age)
[1] 52 63 34 87
attr(,"na.action")
[1] 5
attr(,"class")
[1] "omit"
NaN도 있습니다 0/0 과 같이 계산될 수 없는 값을 의미합니다. NULL은 값이 아예 존재하지 않는 경우를 말합니다. NA는 결측’값’으로 셀 수 있지만 NULL은 셀 수 없습니다.
0/0
[1] NaN
<- c() # 빈 벡터
a # NULL a
NULL
length(a)
[1] 0
<- c(NA) # 결측값 추가
b b
[1] NA
length(b)
[1] 1
List
리스트는 벡터와 다르게 heterogeneous한 데이터를 함께 담을 수 있습니다. 유연하게 데이터를 마구 넣을 수 있기 때문에 일종의 창고로 쓸 수 있습니다. 하지만 가끔 어떤 함수를 쓰면 리스트 형태로 반환 받게 되는데 그럴 때에만 다루고 개인적으로 잘 활용하진 않습니다. 인덱싱은 최초엔 대괄호 두개가 중복된 [[ ]]로 합니다.
<- list(1, "abc", c(3, 4, 5))
list_a list_a
[[1]]
[1] 1
[[2]]
[1] "abc"
[[3]]
[1] 3 4 5
2]] list_a[[
[1] "abc"
리스트는 직접 산술연산이 되지 않습니다. 하고 싶다면 unlist()로 벡터 형태로 바꿔줘야 합니다.
<- list(1,2,3)
list_b #list_b + 1 ## 오류
unlist(list_b) + 1
[1] 2 3 4
Dataframe
R에서 벡터와 더불어 가장 많이 쓰는 구조입니다. 보통 연구할 때 엑셀처럼 테이블 형태의 데이터를 다룰텐데 R에서는 데이터프레임으로 조작하기 때문입니다. 기본적으로 data.frame() 함수로 만듭니다. 함수 안에 컬럼명=데이터를 넣어 만듭니다.
<- data.frame(NAME=c("a", "b", "c","d"), VALUE=c(1, 2, 3, 4), stringsAsFactors = FALSE)
df1 df1
NAME VALUE
1 a 1
2 b 2
3 c 3
4 d 4
저는 data.frame안에 컬럼명은 대문자로 합니다. 데이터와 구분을 잘하기 위해서 입니다. 데이터프레임 안에 특정 변수만 참조하는건 $ 연산자로 합니다.
$NAME df1
[1] "a" "b" "c" "d"
data.frame은 2차원 구조이기 때문에 인덱싱 할때 대괄호로 구분합니다.
2, 1] df1[
[1] "b"
3:nrow(df1),] df1[
NAME VALUE
3 c 3
4 d 4
2] df1[,
[1] 1 2 3 4
인덱싱하여 값을 삽입하는 것도 가능합니다.
2, 1] <- "new"
df1[ df1
NAME VALUE
1 a 1
2 new 2
3 c 3
4 d 4
data.frame을 다루는 자세한 방법은 3강에서 합니다.
Matrix and Array
matrix는 2차원의 행렬, array는 n차원 행렬을 말합니다. matrix는 row와 column의 수를 지정해주어야 합니다. byrow를 FALSE로 하면 column을 기준으로 값을 넣고 TRUE로 하면 row를 기준으로 값을 넣습니다.
<- matrix(1:4, nrow=2, ncol=2, byrow=FALSE)
mat1 <- matrix(1:4, nrow=2, ncol=2, byrow=TRUE)
mat2 mat1
[,1] [,2]
[1,] 1 3
[2,] 2 4
mat2
[,1] [,2]
[1,] 1 2
[2,] 3 4
행렬곱(matix multiplication)은 %*%로 합니다. 단순 *로 한다면 element-wise product (Hadamard product)가 됩니다.
%*% mat2 # matrix multiplication mat1
[,1] [,2]
[1,] 10 14
[2,] 14 20
* mat2 # element-wise product mat1
[,1] [,2]
[1,] 1 6
[2,] 6 16
역행렬은 solve(), 전치(transpose)는 t()를 하면 됩니다.
solve(mat1)
[,1] [,2]
[1,] -2 1.5
[2,] 1 -0.5
t(mat1)
[,1] [,2]
[1,] 1 2
[2,] 3 4
array는 matrix의 일반화, n차원 행렬을 말합니다. 데이터와 차원을 설정해주면 됩니다.
<- array(1:27, dim = c(3, 3, 3)) arr1
3x3x3 행렬이 만들어진 것을 볼 수 있습니다. 데이터는 선행하는 차원을 기준으로 채워넣어진 것을 확인할 수 있습니다.
Factor
범주형 변수가 분석에 사용되기 위해선 더미변수로 변환이 되어야하는데 factor가 그런 역할을 합니다. 범주형 변수를 처리해야하니 잘 쓰이지만… 이 목적을 위해서만 쓰입니다.
<- c("Non", "PPI", "H2RA")
my_vars <- as.factor(my_vars) # character형을 factor로 변환
my_vars_fac my_vars_fac
[1] Non PPI H2RA
Levels: H2RA Non PPI
만약 Non을 reference로 PPI 약제의 상대 위험도, H2RA 약제의 상대 위험도를 구해본다고 했을 때 위와 같이 factor로 정하게 되면 H2RA를 reference로 둔채로 결과가 나오게 됩니다. 알파벳 순으로 level을 처리하기 때문입니다. 저는 보통 reference할 변수 앞에 “0”을 붙여주는 식으로 합니다.
<- paste0(0:2, my_vars)
my_vars <- as.factor(my_vars) # character형을 factor로 변환
my_vars_fac my_vars_fac
[1] 0Non 1PPI 2H2RA
Levels: 0Non 1PPI 2H2RA
위에 data.frame() 함수를 쓸 때 stringsAsFactors = FALSE 옵션을 넣었던 것을 기억하시나요? R버전 3.x.x 에서는 이 함수로 character형 자료를 넣어 저 옵션을 넣지 않은 채로 데이터프레임을 만들게 되면 character형 자료가 factor로 바뀌게 됩니다. 우리는 4.x.x 버전으로 쓸 것이기 때문에 상관은 없지만요. factor형으로 데이터 처리를 하는 것은 적절하지 않습니다. character이나 numeric으로 데이터를 가공해야 합니다.
이외에 여러 data type이나 structure가 있지만 지금 배운 것을 응용해서 만들어진다고 생각하시면 됩니다.
성균관대학교 삼성융합의과학원 의생명통계학 2023년 1학기 - 이영찬